///*******************************************************************************
// * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
// *
// * All rights reserved. This program and the accompanying materials are
// * made available under the terms of the Eclipse Public License v1.0 which
// * accompanies this distribution, and is available at
// * http://www.eclipse.org/legal/epl-v10.html
// *******************************************************************************/
//
//package org.lttng.scope.lami.ui.viewers;
//
//import static java.util.Objects.requireNonNull;
//
//import java.math.BigDecimal;
//import java.text.Format;
//import java.util.ArrayList;
//import java.util.Arrays;
//import java.util.Comparator;
//import java.util.HashMap;
//import java.util.HashSet;
//import java.util.Iterator;
//import java.util.List;
//import java.util.Map;
//import java.util.Set;
//import java.util.stream.Stream;
//
//import org.eclipse.jdt.annotation.Nullable;
//import org.eclipse.swt.SWT;
//import org.eclipse.swt.events.MouseAdapter;
//import org.eclipse.swt.events.MouseEvent;
//import org.eclipse.swt.events.PaintEvent;
//import org.eclipse.swt.events.PaintListener;
//import org.eclipse.swt.graphics.Color;
//import org.eclipse.swt.graphics.GC;
//import org.eclipse.swt.graphics.Point;
//import org.eclipse.swt.graphics.Rectangle;
//import org.eclipse.swt.widgets.Composite;
//import org.eclipse.tracecompass.tmf.core.signal.TmfSignalManager;
//import org.lttng.scope.lami.core.aspect.LamiTableEntryAspect;
//import org.lttng.scope.lami.core.module.LamiChartModel;
//import org.lttng.scope.lami.core.module.LamiTableEntry;
//import org.lttng.scope.lami.core.module.LamiChartModel.ChartType;
//import org.lttng.scope.lami.ui.signals.LamiSelectionUpdateSignal;
//import org.lttng.scope.lami.ui.views.LamiReportViewTabPage;
//import org.swtchart.IAxis;
//import org.swtchart.IAxisTick;
//import org.swtchart.IBarSeries;
//import org.swtchart.ISeries;
//import org.swtchart.ISeries.SeriesType;
//import org.swtchart.Range;
//
//import com.google.common.collect.Iterators;
//
///**
// * Bar chart Viewer for LAMI views.
// *
// * @author Alexandre Montplaisir
// * @author Jonathan Rajotte-Julien
// * @author Mathieu Desnoyers
// */
//public class LamiBarChartViewer extends LamiXYChartViewer {
//
//    private static final double LOGSCALE_EPSILON_FACTOR = 100.0;
//
//    private class Mapping {
//        final private @Nullable Integer fInternalValue;
//        final private @Nullable Integer fModelValue;
//
//        public Mapping(@Nullable Integer internalValue, @Nullable Integer modelValue) {
//            fInternalValue = internalValue;
//            fModelValue = modelValue;
//        }
//
//        public @Nullable Integer getInternalValue() {
//            return fInternalValue;
//        }
//
//        public @Nullable Integer getModelValue() {
//            return fModelValue;
//        }
//    }
//
//    private final String[] fCategories;
//    private final Map<ISeries, List<Mapping>> fIndexPerSeriesMapping;
//    private final Map<LamiTableEntry, Mapping> fEntryToCategoriesMap;
//
//    private LamiGraphRange fYInternalRange = new LamiGraphRange(requireNonNull(BigDecimal.ZERO), requireNonNull(BigDecimal.ONE));
//    private LamiGraphRange fYExternalRange;
//
//
//    /**
//     * Creates a bar chart Viewer instance based on SWTChart.
//     *
//     * @param parent
//     *            The parent composite to draw in.
//     * @param page
//     *            The {@link LamiReportViewTabPage} parent page
//     * @param chartModel
//     *            The information about the chart to build
//     */
//    public LamiBarChartViewer(Composite parent, LamiReportViewTabPage page, LamiChartModel chartModel) {
//        super(parent, page, chartModel);
//
//        List<LamiTableEntryAspect> xAxisAspects = getXAxisAspects();
//        List<LamiTableEntryAspect> yAxisAspects = getYAxisAspects();
//
//        /* bar chart cannot deal with multiple X series */
//        if (getChartModel().getChartType() != ChartType.BAR_CHART && xAxisAspects.size() != 1) {
//            throw new IllegalArgumentException("Invalid configuration passed to a bar chart."); //$NON-NLS-1$
//        }
//
//        /* Enable categories */
//        getChart().getAxisSet().getXAxis(0).enableCategory(true);
//
//        LamiTableEntryAspect xAxisAspect = xAxisAspects.get(0);
//        List<LamiTableEntry> entries = getResultTable().getEntries();
//        boolean logscale = chartModel.yAxisIsLog();
//        fIndexPerSeriesMapping = new HashMap<>();
//        fEntryToCategoriesMap = new HashMap<>();
//
//        /* Categories index mapping */
//        Format formatter = null;
//        if (xAxisAspect.isContinuous()) {
//            formatter = getContinuousAxisFormatter(xAxisAspects, entries, null, null);
//        }
//
//        List<@Nullable String> xCategories = new ArrayList<>();
//        for (int i = 0; i < entries.size(); i++) {
//            String string = xAxisAspect.resolveString(entries.get(i));
//            if (string == null) {
//                fEntryToCategoriesMap.put(entries.get(i), new Mapping(null, i));
//                continue;
//            }
//
//            fEntryToCategoriesMap.put(entries.get(i), new Mapping(xCategories.size(), i));
//            if (formatter != null) {
//                string = formatter.format(xAxisAspect.resolveNumber(entries.get(i)));
//            }
//
//            xCategories.add(string);
//
//        }
//        fCategories = xCategories.toArray(new String[0]);
//
//        /* The y values range */
//        /* Clamp minimum to zero or negative value */
//        fYExternalRange = getRange(yAxisAspects, true);
//
//        /*
//         * Log scale magic course 101:
//         *
//         * It uses the relative difference divided by a factor
//         * (100) to get as close as it can to the actual minimum but still a
//         * little bit smaller. This is used as a workaround of SWTCHART
//         * limitations regarding custom scale drawing in log scale mode, bogus
//         * representation of NaN double values and limited support of multiple
//         * size series.
//         *
//         * This should be good enough for most users.
//         */
//        double min = Double.MAX_VALUE;
//        double max = Double.MIN_VALUE;
//        double logScaleEpsilon = ZERO_DOUBLE;
//        if (logscale) {
//
//            /* Find minimum and maximum values excluding <= 0 values */
//            for (LamiTableEntryAspect aspect : yAxisAspects) {
//                for (LamiTableEntry entry : entries) {
//                    Number externalValue = aspect.resolveNumber(entry);
//                    if (externalValue == null) {
//                        continue;
//                    }
//                    Double value = getInternalDoubleValue(externalValue, fYInternalRange, fYExternalRange);
//                    if (value <= 0) {
//                        continue;
//                    }
//                    min = Math.min(min, value);
//                    max = Math.max(max, value);
//                }
//            }
//
//            if (min == Double.MAX_VALUE) {
//                /* Series are empty in log scale*/
//                return;
//            }
//
//            double delta = max - min;
//            logScaleEpsilon = min - ((min * delta) / (LOGSCALE_EPSILON_FACTOR * max));
//        }
//
//        for (LamiTableEntryAspect yAxisAspect : yAxisAspects) {
//            if (!yAxisAspect.isContinuous() || yAxisAspect.isTimeStamp()) {
//                /* Only plot continuous aspects */
//                continue;
//            }
//
//            List<Double> validXValues = new ArrayList<>();
//            List<Double> validYValues = new ArrayList<>();
//            List<Mapping> indexMapping = new ArrayList<>();
//
//            for (int i = 0; i < entries.size(); i++) {
//                Integer categoryIndex = requireNonNull(fEntryToCategoriesMap.get(requireNonNull(entries.get(i)))).fInternalValue;
//
//                if (categoryIndex == null) {
//                    /* Invalid value do not show */
//                    continue;
//                }
//
//                Double yValue = ZERO_DOUBLE;
//                @Nullable Number number = yAxisAspect.resolveNumber(entries.get(i));
//
//                if (number == null) {
//                    /*
//                     * Null value for y is the same as zero since this is a bar
//                     * chart
//                     */
//                    yValue = ZERO_DOUBLE;
//                } else {
//                    yValue = getInternalDoubleValue(number, fYInternalRange, fYExternalRange);
//                }
//
//                if (logscale && yValue <= ZERO_DOUBLE) {
//                    /*
//                     * Less or equal to 0 values can't be plotted on a log
//                     * scale. We map them to the mean of the >=0 minimal value
//                     * and the calculated log scale magic epsilon.
//                     */
//                    yValue = (min + logScaleEpsilon) / 2.0;
//                }
//
//                validXValues.add(requireNonNull(categoryIndex).doubleValue());
//                validYValues.add(yValue.doubleValue());
//                indexMapping.add(new Mapping(categoryIndex, requireNonNull(fEntryToCategoriesMap.get(requireNonNull(entries.get(i)))).fModelValue));
//            }
//
//            String name = yAxisAspect.getLabel();
//
//            if (validXValues.isEmpty() || validYValues.isEmpty()) {
//                /* No need to plot an empty series */
//                continue;
//            }
//
//            IBarSeries barSeries = (IBarSeries) getChart().getSeriesSet().createSeries(SeriesType.BAR, name);
//            barSeries.setXSeries(validXValues.stream().mapToDouble(Double::doubleValue).toArray());
//            barSeries.setYSeries(validYValues.stream().mapToDouble(Double::doubleValue).toArray());
//            fIndexPerSeriesMapping.put(barSeries, indexMapping);
//        }
//
//        setBarSeriesColors();
//
//        /* Set all y axis logscale mode */
//        Stream.of(getChart().getAxisSet().getYAxes()).forEach(axis -> axis.enableLogScale(logscale));
//
//        /* Set the formatter on the Y axis */
//        IAxisTick yTick = getChart().getAxisSet().getYAxis(0).getTick();
//        yTick.setFormat(getContinuousAxisFormatter(yAxisAspects, entries, fYInternalRange, fYExternalRange));
//
//        /*
//         * SWTChart workaround: SWTChart fiddles with tick mark visibility based
//         * on the fact that it can parse the label to double or not.
//         *
//         * If the label happens to be a double, it checks for the presence of
//         * that value in its own tick labels to decide if it should add it or
//         * not. If it happens that the parsed value is already present in its
//         * map, the tick gets a visibility of false.
//         *
//         * The X axis does not have this problem since SWTCHART checks on label
//         * angle, and if it is != 0 simply does no logic regarding visibility.
//         * So simply set a label angle of 1 to the axis.
//         */
//        yTick.setTickLabelAngle(1);
//
//        /* Adjust the chart range */
//        getChart().getAxisSet().adjustRange();
//
//        if (logscale && logScaleEpsilon != max) {
//            getChart().getAxisSet().getYAxis(0).setRange(new Range(logScaleEpsilon, max));
//        }
//
//        /* Once the chart is filled, refresh the axis labels */
//        refreshDisplayLabels();
//
//        /* Add mouse listener */
//        getChart().getPlotArea().addMouseListener(new LamiBarChartMouseDownListener());
//
//        /* Custom Painter listener to highlight the current selection */
//        getChart().getPlotArea().addPaintListener(new LamiBarChartPainterListener());
//    }
//
//    private final class LamiBarChartMouseDownListener extends MouseAdapter {
//
//        @Override
//        public void mouseDown(@Nullable MouseEvent event) {
//            if (event == null || event.button != 1) {
//                return;
//            }
//
//            boolean ctrlMode = false;
//            int xMouseLocation = event.x;
//            int yMouseLocation = event.y;
//
//            Set<Integer> selections;
//            if ((event.stateMask & SWT.CTRL) != 0) {
//                ctrlMode = true;
//                selections = getSelection();
//            } else {
//                /* Reset selection state */
//                unsetSelection();
//                selections = new HashSet<>();
//            }
//
//            ISeries[] series = getChart().getSeriesSet().getSeries();
//
//            /*
//             * Iterate over all series, get the rectangle bounds for each
//             * category, and find the category index under the mouse.
//             *
//             * Since categories map directly to the index of the fResultTable
//             * and that this table is immutable the index of the entry
//             * corresponds to the categories index. Signal to all LamiViewer and
//             * LamiView the update of selection.
//             */
//            for (ISeries oneSeries : series) {
//                IBarSeries barSerie = ((IBarSeries) oneSeries);
//                Rectangle[] recs = barSerie.getBounds();
//
//                for (int j = 0; j < recs.length; j++) {
//                    Rectangle rectangle = recs[j];
//                    if (rectangle.contains(xMouseLocation, yMouseLocation)) {
//                        int index = getTableEntryIndexFromGraphIndex(requireNonNull(oneSeries), j);
//                        if (!ctrlMode || (index >= 0 && !selections.remove(index))) {
//                            selections.add(index);
//                        }
//                    }
//                }
//            }
//
//            /* Save the current selection internally */
//            setSelection(selections);
//            /* Signal all Lami viewers & views of the selection */
//            LamiSelectionUpdateSignal signal = new LamiSelectionUpdateSignal(this,
//                    selections, getPage());
//            TmfSignalManager.dispatchSignal(signal);
//            redraw();
//        }
//    }
//
//    @Override
//    protected void redraw() {
//        setBarSeriesColors();
//        super.redraw();
//    }
//
//    /**
//     * Set the chart series colors according to the selection state. Use light
//     * colors when a selection is present.
//     */
//    private void setBarSeriesColors() {
//        Iterator<Color> colorsIt;
//
//        if (isSelected()) {
//            colorsIt = Iterators.cycle(LIGHT_COLORS);
//        } else {
//            colorsIt = Iterators.cycle(COLORS);
//        }
//
//        for (ISeries series : getChart().getSeriesSet().getSeries()) {
//            ((IBarSeries) series).setBarColor(colorsIt.next());
//        }
//    }
//
//    private final class LamiBarChartPainterListener implements PaintListener {
//        @Override
//        public void paintControl(@Nullable PaintEvent e) {
//            if (e == null || !isSelected()) {
//                return;
//            }
//
//            Iterator<Color> colorsIt = Iterators.cycle(COLORS);
//            GC gc = e.gc;
//
//            for (ISeries s : getChart().getSeriesSet().getSeries()) {
//                ISeries series = requireNonNull(s);
//                Color color = colorsIt.next();
//                for (int index : getSelection()) {
//                    int graphIndex = getGraphIndexFromTableEntryIndex(series, index);
//                    if (graphIndex < 0) {
//                        /* Invalid index */
//                        continue;
//                    }
//
//                    Rectangle[] bounds = ((IBarSeries) series).getBounds();
//                    if (bounds.length != fCategories.length) {
//                        /*
//                         * The plot is too cramped and SWTChart currently does
//                         * its best on rectangle drawing and returns the
//                         * rectangle that it is able to draw.
//                         *
//                         * For now we simply do not draw since it is really hard
//                         * to see anyway. A better way to visualize the value
//                         * would be a full cross for each selection based on
//                         * their coordinates.
//                         */
//                        continue;
//                    }
//                    Rectangle rectangle = bounds[graphIndex];
//                    gc.setBackground(color);
//                    gc.fillRectangle(rectangle);
//                }
//            }
//        }
//    }
//
//    @Override
//    protected void refreshDisplayLabels() {
//        /* Only if we have at least 1 category */
//        if (fCategories.length == 0) {
//            return;
//        }
//
//        /* Only refresh if labels are visible */
//        IAxis xAxis = getChart().getAxisSet().getXAxis(0);
//        if (!xAxis.getTick().isVisible() || !xAxis.isCategoryEnabled()) {
//            return;
//        }
//
//        /*
//         * Shorten all the labels to 5 characters plus "…" when the longest
//         * label length is more than 50% of the chart height.
//         */
//
//        Rectangle rect = getChart().getClientArea();
//        int lengthLimit = (int) (rect.height * 0.40);
//
//        GC gc = new GC(fParent);
//        gc.setFont(xAxis.getTick().getFont());
//
//        /* Find the longest category string */
//        String longestString = Arrays.stream(fCategories).max(Comparator.comparingInt(String::length)).orElse(fCategories[0]);
//
//        /* Get the length and height of the longest label in pixels */
//        Point pixels = gc.stringExtent(longestString);
//
//        // Completely arbitrary
//        int cutLen = 5;
//
//        String[] displayCategories = new String[fCategories.length];
//        if (pixels.x > lengthLimit) {
//            /* We have to cut down some strings */
//            for (int i = 0; i < fCategories.length; i++) {
//                if (fCategories[i].length() > cutLen) {
//                    displayCategories[i] = fCategories[i].substring(0, cutLen) + ELLIPSIS;
//                } else {
//                    displayCategories[i] = fCategories[i];
//                }
//            }
//        } else {
//            /* All strings should fit */
//            displayCategories = Arrays.copyOf(fCategories, fCategories.length);
//        }
//        xAxis.setCategorySeries(displayCategories);
//
//        /* Cleanup */
//        gc.dispose();
//    }
//
//    private int getTableEntryIndexFromGraphIndex(ISeries series, int index) {
//        List<Mapping> indexes = fIndexPerSeriesMapping.get(series);
//        if (indexes == null || index > indexes.size() || index < 0) {
//            return -1;
//        }
//
//        Mapping mapping = indexes.get(index);
//        Integer modelValue = mapping.getModelValue();
//        if (modelValue != null) {
//            return modelValue.intValue();
//        }
//        return -1;
//    }
//
//    private int getGraphIndexFromTableEntryIndex(ISeries series, int index) {
//        List<Mapping> indexes = fIndexPerSeriesMapping.get(series);
//        if (indexes == null || index < 0) {
//            return -1;
//        }
//
//        int internalIndex = -1;
//        for (Mapping mapping : indexes) {
//            if (mapping.getModelValue() == index) {
//                Integer internalValue = mapping.getInternalValue();
//                if (internalValue != null) {
//                    internalIndex = internalValue.intValue();
//                    break;
//                }
//            }
//        }
//        return internalIndex;
//    }
//}
